package com.prac.redis.controller;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * redis-实现分布式锁
 *
 * @author hxg
 * @version 1.0
 * @date 2022/5/3 22:28
 */
@Slf4j
@RestController
@RequestMapping("/lock")
public class RedisLockController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 原生api操作——获取分布式锁
     */
    @GetMapping("test")
    public void distributeLock(@RequestParam("pid") Long pid) {
        String key = "lock:test:" + pid;
        String uuid = UUID.randomUUID().toString();
        lock(key, uuid);
    }

    final static String SCRIPT = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

    private void lock(String key, String uuid) {
        // 设置锁，value是唯一标识，并配置过期时间
        Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, 10, TimeUnit.MINUTES);
        log.info("线程：{}|<|>|uuid：{}|<|>|加锁状态:{}", Thread.currentThread().getId(), uuid, isLock);
        if (Boolean.TRUE.equals(isLock)) {
            try {
                log.info("线程：{}|<|>|uuid：{}|<|>|业务逻辑处理====>", Thread.currentThread().getId(), uuid);
                TimeUnit.SECONDS.sleep(20);
            } catch (InterruptedException e) {
                log.info("业务异常", e);
            } finally {
                // lua脚本保证判断锁owner并且删除，过程的原子性
                Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(SCRIPT, Long.class), Arrays.asList(key), uuid);
                log.info("线程：{}|<|>|uuid：{}|<|>|解锁状态:{}", Thread.currentThread().getId(), uuid, result);
            }
        } else {
            //自旋争取
            try {
                log.info("线程：{}|<|>|uuid：{}|<|>|开始自旋争取", Thread.currentThread().getId(), uuid);
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                log.info("", e);
            }
            lock(key, uuid);
        }
    }

    @Resource
    private RedissonClient redissonClient;

    @GetMapping("test2")
    public void redissonLock(@RequestParam("pid") Long pid) {
        String key = "lock:test:" + pid;
        RLock rLock = redissonClient.getLock(key);
        try {
            boolean isLocked = rLock.tryLock(10, TimeUnit.SECONDS);
            if (isLocked) {
                try {
                    log.info("线程：{}|<|>|uuid：{}|<|>|业务逻辑处理====>", Thread.currentThread().getId(), key);
                    TimeUnit.SECONDS.sleep(20);
                } catch (InterruptedException e) {
                    log.info("", e);
                }
            }
        } catch (Exception e) {
            rLock.unlock();
            log.info("线程：{}|<|>|key：{}|<|>|解锁状态:{}", Thread.currentThread().getId(), key, rLock.isLocked());
        } finally {
            log.info("线程：{}|<|>|uuid：{}|<|>|业务逻辑处理完成", Thread.currentThread().getId(), key);
        }
    }

}

